/* eslint-disable @typescript-eslint/no-explicit-any */
import { MetadataKey } from './enum';
import { Constructor } from './interfaces';
import { isArray, isFunction } from './utils';

/**
 * getInjectValues
 * @param target
 * @param propertyName
 * @returns
 */
export const getInjectValues = (target: object, propertyName: string): any[] => {
    const data: Record<string, any> =
        Reflect.getMetadata(MetadataKey.ROUTE_ARGS_METADATA, target.constructor, propertyName) || {};
    const values: any[] = (Object.values(data) || []).sort((a, b) => a.index - b.index);
    return values;
};

/**
 * OverrideReqEffect
 * @param values
 * @param args
 * @returns
 */
export function OverrideReqEffect(values: any[], args: any[]) {
    const executionContext: any = {
        switchToHttp() {
            return {
                getRequest() {
                    return args[0];
                },
            };
        },
    };
    return args.map((param, index) => {
        const item = values.find((_) => _.index === index);
        if (item.data) {
            const registerClasses = item?.pipes?.map((target: any) =>
                isFunction(target) ? new (<Constructor<any>>target)() : target,
            );
            if (isArray(item.data)) {
                const _param = (<string[]>item.data).reduce((prev, next) => {
                    const paramObj = <Record<string, any>>{};
                    if (registerClasses?.length) {
                        registerClasses.forEach((target: any) => {
                            paramObj[next] =
                                target?.transform?.(paramObj[next]) ?? (param[next] || target.defaultValue);
                        });
                    } else {
                        paramObj[next] = param[next];
                    }
                    return { ...prev, ...paramObj };
                }, {});
                return item?.factory?.(item.data, executionContext) ?? _param;
            }
            registerClasses?.forEach((target: any) => {
                param[<string>item.data] =
                    target?.transform?.(param[<string>item.data]) ?? (param[<string>item.data] || target.defaultValue);
            });
            return item?.factory?.(item.data, executionContext) ?? param?.[<string>item.data] ?? void 0;
        }
        return item?.factory?.(void 0, executionContext) ?? param;
    });
}

export function Override() {
    return function (target: any, key: string, descriptor: PropertyDescriptor) {
        const originalMethod = descriptor.value;
        const values = getInjectValues(target, <string>key);
        descriptor.value = function (...args: any[]) {
            return originalMethod.apply(this, values.length ? OverrideReqEffect(values, args) : args);
        };
    };
}
